package cn.demoncat.util.xml;

import cn.demoncat.util.exception.IoRuntimeException;
import cn.demoncat.util.lang.ArrayUtil;
import cn.demoncat.util.lang.CollectionUtil;
import cn.demoncat.util.lang.MapUtil;
import cn.demoncat.util.lang.StringUtil;
import cn.demoncat.util.lang.constant.StringConstant;
import cn.demoncat.util.lang.entity.MapSo;
import cn.demoncat.util.xml.entity.*;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.XmlFriendlyNameCoder;
import com.thoughtworks.xstream.io.xml.XppDriver;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import org.xml.sax.InputSource;

import javax.xml.parsers.DocumentBuilderFactory;
import java.io.ByteArrayInputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.*;
import java.util.Map.Entry;

/**
 * XML工具
 * 
 * <pre>
 * 注解说明：
 * XStreamAsAttribute  	将类的属性，作为父节点的属性
 * XStreamAlias			定义类的别名、属性的别名
 * XStreamConverter 	指定类或属性的转换器
 * XStreamImplicit 		注释集合属性，去除类的标签；注解Map属性，设置key的名称
 *
 * MAP解析注意：
 * 1、MAP格式：Map<String,Object>、Map<String,List>、Map<String,String>
 * 2、解析List属性：元素为多个同名节点，如 <list>1<list/><list>2</list>
 * 3、反解析问题：
 * 		1.无法区分源类型，只有三种格式：Map<String,Object>、Map<String,List>、Map<String,String>
 * 		2.如果List值只有1个元素，会解析为：Map<String,Object>、Map<String,String>
 * 
 * 对象解析注意：
 * 1、反解析时的设置参数必须和与解析时相同。
 * 2、解析Map属性：默认Map类型以Entry作为节点；如果是MapSs则以key作为节点（属性声明类型应为MapSs，否则会给标签添加class属性）。
 * 3、解析List属性：默认"<集合><元素类>值</集合></元素类>"格式，可以通过XStreamCollectionParam或@XStreamImplicit合并为"<集合>值</集合><集合>值</集合>"。
 * 4、设置别名：通过XStreamClassAliasParam设置主类和集合元素类的别名，通过XStreamFieldAliasParam设置字段的别名，也可以通过@XStreamAlias注解实体类来设置。
 *
 * 集合解析-1：
 * 属性 = List<El> els;
 * XML = <els><el><id>1<id/></el></els>
 * 处理 = 使用@XStreamAlias("el")注解El类
 *
 * 集合解析-2：
 * 属性 = List<El> els;
 * XML = <els><id>1<id/></els>
 * 处理 = 使用@XStreamImplicit注解els属性
 *
 * 非空限制：
 * 转换其它类型时，如果XML值为空，会 throw new NumberFormatException("Zero length string")，需要定义并绑定Converter
 *
 * </pre>
 * 
 * @author 延晓磊
 *
 * @since 2019年10月10日
 */
public class XmlUtil {
	
	// XML声明
	private static final String XML_DECLARATION = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
	// 空参数
	private static final List<XStreamFieldAliasParam> EMPTY_XFA = new ArrayList<>();
	// 支持的类型
	private static final String[] ALLOW_TYPE = new String[]{".*"};
	
 
	/**
	 * XML转Map
	 * 
	 * @param xml
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年10月11日
	 */
	public static Map<String, Object> toMap(String xml){
		Map<String, Object> map = new HashMap<>();
		// 读取XML
		Document dom = readXml(xml);
		// 获取根节点
		Element root = dom.getRootElement();
		// 填充节点到Map
		return toMap(root,map);
	}
	 
	/**
	 * XML转Map：只获取二级节点
	 * 
	 * @param xml
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年10月11日
	 */
	@SuppressWarnings("unchecked")
	public static Map<String, String> toMapStr(String xml){
		Map<String, String> map = new HashMap<>();
		// 读取XML
		Document dom = readXml(xml);
		// 获取根节点
		Element root = dom.getRootElement();
		// 获取子节点
		List<Element> elements = root.elements();
		for (Element element : elements) {
			map.put(element.getName(), element.getTextTrim());
		}
		return map;
	}
	
	/**
	 * 读取XML节点到Map
	 * 
	 * @param dom
	 * @param map
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年10月11日
	 */
	@SuppressWarnings("unchecked")
	public static Map<String, Object> toMap(Element dom, Map<String, Object> map) {
    	// 获取子节点
    	List<Element> elements = dom.elements();
    	// 没有子节点
    	if (elements.size() == 0) {
    		// 填充值
    		map.put(dom.getName(), dom.getTextTrim());
		}else {
			// 遍历子节点
			Map<String, Object> map1 = new HashMap<>();
			List<String> list1;
			List<Map<String, Object>> list2;
			Object obj;
			Object obj1;
			// 遍历节点
			for (Element el : elements) {
				// 获取缓存
				obj = map1.get(el.getName());
				// 首次直接填充
				if (obj == null) {
					toMap(el, map1);
				}else {
					if (obj instanceof String) {
						// 填充 > String 缓存
						toMap(el, map1);
						// 整合 > List 缓存
						list1 = new ArrayList<>();
						list1.add(obj.toString());
						list1.add(map1.get(el.getName()).toString());
						map1.put(el.getName(), list1);
					}else if (obj instanceof Map) {
						// 填充 > Map 缓存
						toMap(el, map1);
						// 整合 > List 缓存
						list2 = new ArrayList<>();
						list2.add((Map<String, Object>)obj);
						list2.add((Map<String, Object>)(map1.get(el.getName())));
						map1.put(el.getName(), list2);
					}else if (obj instanceof List) {
						// 填充 > String/Map
						toMap(el, map1);
						obj1 = map1.get(el.getName());
						if (obj1 instanceof String) {
							// 整合 > List 缓存
							list1 = (List<String>)obj;
							list1.add(obj1.toString());
							map1.put(el.getName(), list1);
						}else if (obj1 instanceof Map) {
							// 整合 > List 缓存
							list2 = (List<Map<String,Object>>)obj;
							list2.add((Map<String, Object>)obj1);
							map1.put(el.getName(), list2);
						}
					}
				}
            }
			// 整合子Map
			map.put(dom.getName(), map1);
		}
		return map;
	}

	/**
	 * 读取XML，获取Domcumnet
	 * 
	 * @param xml
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年10月11日
	 */
	public static Document readXml(String xml) {
		try {
			return new SAXReader().read(new ByteArrayInputStream(StringUtil.getBytes(xml.trim())));
		} catch (DocumentException e) {
			throw new IoRuntimeException("读取XML失败", e);
		}
	}
		
	/**
	 * 添加XML声明
	 * 
	 * @param xml
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年10月11日
	 */
	public static String addDeclaration(String xml) {
		return addDeclaration(xml, XML_DECLARATION);
	}

	/**
	 * 添加XML声明
	 *
	 * @param xml
	 * @param declaration
	 * @return
	 *
	 * @author 延晓磊
	 *
	 * @since 2019年10月11日
	 */
	public static String addDeclaration(String xml, String declaration) {
		xml = xml.trim();
		if (!xml.startsWith("<?xml")) {
			return declaration + StringConstant.NEW_LINE + xml;
		}
		return xml;
	}

	/**
	 * Map转XML
	 * 
	 * @param map	源数据，支持多层级：Map<Map,List<Map>,String>
	 * @param root	根标签名称
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年10月10日
	 */
	public static String toXml(Map<?, ?> map, String root) {
		return toXml(MapSo.getInstance(root,map));
	}
	
	/**
	 * Map转XML
	 * 
	 * @param map	源数据，支持多层级，必须有1个根元素：Map<Map,List<Map>,String>
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年10月10日
	 */
	public static String toXml(Map<?, ?> map) {
		if (MapUtil.isEmpty(map)) {
			return StringConstant.EMPTY;
		}
		return toXml(map, new StringBuilder());
	}

	/**
	 * 判断是否为XML
	 * 
	 * @param xml
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年10月10日
	 */
	public static boolean isXml(String xml) {
		// Dom工厂
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		try {
			// 解析
			factory.newDocumentBuilder().parse(new InputSource(new StringReader(xml)));
		} catch (Exception e) {
			// 解析失败
			return false;
		}
		return true;
	}

	/**
	 * 格式化XML
	 * 
	 * @param xml
	 * 
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年10月10日
	 */
	public static String format(String xml) {
		return format(xml, true, true);
	}

	/**
	 * 格式化XML
	 * 
	 * @param xml
	 * @param pretty		true = 格式化；false = 压缩
	 * @param declaration	是否禁用XML声明：false = 前缀XML_DECLARATION
	 * 
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年10月10日
	 */
	public static String format(String xml, boolean pretty, boolean declaration) {
		// 非XML
		if (!isXml(xml)) {
			return xml;
		}
		try {
			// 读取XML
			Document document = DocumentHelper.parseText(xml);
			// 格式化
			OutputFormat format = pretty ? OutputFormat.createPrettyPrint() : OutputFormat.createCompactFormat();
			// 禁用XML声明
			format.setSuppressDeclaration(declaration);
			// 输出
			StringWriter writer = new StringWriter();
			XMLWriter xmlWriter = new XMLWriter(writer, format);
			xmlWriter.write(document);
			xmlWriter.close();
			return writer.toString().trim();
		} catch (Exception e) {
			throw new IoRuntimeException("格式化XML失败", e);
		}
	}

	/**
	 * 简单对象 转 XML
	 * 
	 * @param obj				对象：主类为根节点，排除null和serialVersionUID字段
	 * @param classAlias		类标签别名：包含主类；也可以通过类注解@XStreamAlias指定
	 * 
	 * @return xml
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年10月10日
	 */
	public static String toXml(Object obj, String classAlias) {
		return toXml(obj, classAlias, EMPTY_XFA);
	}

	/**
	 * 简单对象 转 XML
	 * 
	 * @param obj				对象：主类为根节点，排除null和serialVersionUID字段
	 * @param classAlias		类标签别名：包含主类；也可以通过类注解@XStreamAlias指定
	 * @param fieldAlias		字段标签别名：包含主类的字段，别名为null表示排除字段；也可以通过类字段注解@XStreamAlias指定
	 * 
	 * @return xml
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年10月10日
	 */
	public static String toXml(Object obj, String classAlias, Map<String, String> fieldAlias) {
		return toXml(obj, classAlias, CollectionUtil.isEmpty(fieldAlias) ? EMPTY_XFA : CollectionUtil.toList(new XStreamFieldAliasParam(obj.getClass(), fieldAlias)));
	}
	
	/**
	 * 复合对象 转 XML
	 * 
	 * @param obj				对象：主类为根节点，排除null和serialVersionUID字段
	 * @param classAlias		类标签别名：包含主类；也可以通过类注解@XStreamAlias指定
	 * @param fieldAlias		字段标签别名：包含主类、属性类、集合属性元素类的字段，别名为null表示排除字段；也可以通过类字段注解@XStreamAlias指定
	 * 
	 * @return xml
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年10月10日
	 */
	public static String toXml(Object obj, String classAlias, List<XStreamFieldAliasParam> fieldAlias) {
		return toXml(obj, classAlias, fieldAlias, null);
	}
	
	/**
	 * 复合对象 转 XML
	 * 
	 * @param obj				对象：主类为根节点，排除null和serialVersionUID字段
	 * @param classAlias		类标签别名：包含主类；也可以通过类注解@XStreamAlias指定
	 * @param fieldAlias		字段标签别名：包含主类、属性类、集合属性元素类的字段，别名为null表示排除字段；也可以通过类字段注解@XStreamAlias指定
	 * @param collectionMerges	集合合并处理：将默认的"<集合><元素类>值</集合></元素类>"格式合并为"<集合>值</集合><集合>值</集合>"
	 * 
	 * @return xml
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年10月10日
	 */
	public static String toXml(Object obj, String classAlias, List<XStreamFieldAliasParam> fieldAlias, List<XStreamCollectionParam> collectionMerges) {
		return toXml(obj, classAlias == null ? null : CollectionUtil.toList(new XStreamClassAliasParam(obj.getClass(), classAlias)), fieldAlias, collectionMerges);
	}

	/**
	 * 复合对象 转 XML
	 * 
	 * @param obj				对象：主类为根节点，排除null和serialVersionUID字段
	 * @param classAlias		类标签别名：包含主类、集合属性元素类；也可以通过类注解@XStreamAlias指定
	 * @param fieldAlias		字段标签别名：包含主类、属性类、集合属性元素类的字段，别名为null表示排除字段；也可以通过类字段注解@XStreamAlias指定
	 * @param collectionMerges	集合合并处理：将默认的"<集合><元素类>值</集合></元素类>"格式合并为"<集合>值</集合><集合>值</集合>"
	 * 
	 * @return xml
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年10月10日
	 */
	public static String toXml(Object obj, List<XStreamClassAliasParam> classAlias, List<XStreamFieldAliasParam> fieldAlias, List<XStreamCollectionParam> collectionMerges) {
		// 转换为XML：为所有非null属性创建标签
		return getXStream(classAlias, fieldAlias, collectionMerges).toXML(obj);
	}

	/**
	 * XML 转 简单对象
	 * 
	 * @param xml				XML文本
	 * @param classAlias		类标签别名：包含主类；也可以通过类注解@XStreamAlias指定
	 * 
	 * @return obj
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年10月10日
	 */
	public static <T> T toObj(String xml, XStreamClassAliasParam classAlias) {
		return toObj(xml, classAlias, EMPTY_XFA);
	}
	
	/**
	 * XML 转 简单对象
	 * 
	 * @param xml				XML文本
	 * @param classAlias		类标签别名：包含主类；也可以通过类注解@XStreamAlias指定
	 * @param fieldAlias		字段标签别名：包含主类、属性类、集合属性元素类的字段，别名为null表示排除字段；也可以通过类字段注解@XStreamAlias指定
	 * 
	 * @return obj
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年10月10日
	 */
	public static <T> T toObj(String xml, XStreamClassAliasParam classAlias, Map<String, String> fieldAlias) {
		return toObj(xml, classAlias, (classAlias == null || classAlias.getClazz() == null || CollectionUtil.isEmpty(fieldAlias)) ? EMPTY_XFA : CollectionUtil.toList(new XStreamFieldAliasParam(classAlias.getClazz(), fieldAlias)));
	}
	
	/**
	 * XML 转 复合对象
	 * 
	 * @param xml				XML文本
	 * @param classAlias		类标签别名：包含主类；也可以通过类注解@XStreamAlias指定
	 * @param fieldAlias		字段标签别名：包含主类、属性类、集合属性元素类的字段，别名为null表示排除字段；也可以通过类字段注解@XStreamAlias指定
	 * 
	 * @return obj
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年10月10日
	 */
	public static <T> T toObj(String xml, XStreamClassAliasParam classAlias, List<XStreamFieldAliasParam> fieldAlias) {
		return toObj(xml, classAlias, fieldAlias, null);
	}
 
	/**
	 * XML 转 复合对象
	 * 
	 * @param xml				XML文本
	 * @param classAlias		类标签别名：包含主类；也可以通过类注解@XStreamAlias指定
	 * @param fieldAlias		字段标签别名：包含主类、属性类、集合属性元素类的字段，别名为null表示排除字段；也可以通过类字段注解@XStreamAlias指定
	 * @param collectionMerges	集合合并处理：将默认的"<集合><元素类>值</元素类></集合>"格式合并为"<集合>值</集合><集合>值</集合>"
	 * 
	 * @return obj
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年10月10日
	 */
	public static <T> T toObj(String xml, XStreamClassAliasParam classAlias, List<XStreamFieldAliasParam> fieldAlias, List<XStreamCollectionParam> collectionMerges) {
		return toObj(xml, classAlias == null ? null : CollectionUtil.toList(classAlias), fieldAlias, collectionMerges);
	}
	
	/**
	 * XML 转 复合对象
	 * 
	 * @param xml				XML文本
	 * @param classAlias		类标签别名：包含主类、集合属性元素类；也可以通过类注解@XStreamAlias指定
	 * @param fieldAlias		字段标签别名：包含主类、属性类、集合属性元素类的字段，别名为null表示排除字段；也可以通过类字段注解@XStreamAlias指定
	 * @param collectionMerges	集合合并处理：将默认的"<集合><元素类>值</元素类></集合>"格式合并为"<集合>值</集合><集合>值</集合>"
	 * 
	 * @return obj
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年10月10日
	 */
	@SuppressWarnings("unchecked")
	public static <T> T toObj(String xml, List<XStreamClassAliasParam> classAlias, List<XStreamFieldAliasParam> fieldAlias, List<XStreamCollectionParam> collectionMerges) {
		// 获取XStream
		XStream xStream = getXStream(classAlias, fieldAlias, collectionMerges);
		// 忽略未知字段
		xStream.ignoreUnknownElements();
		// 转换为对象
		return (T)xStream.fromXML(xml);
	}
	
	
	
	
	/**
	 * Map转XML
	 * 
	 * @param map	源数据，支持多层级
	 * @param str	字符串缓存	
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年10月10日
	 */
	private static String toXml(Map<?, ?> map, StringBuilder str) {
		// 遍历MAP
		for (Entry<?, ?> entry : map.entrySet()) {
			// 忽略空值
			if (entry.getValue() == null) {
				continue;
			}
			// 判断类型
			if (MapUtil.isMap(entry.getValue())) {
				// 子节点：Map
				str.append("<").append(entry.getKey()).append(">");
				toXml((Map<?, ?>) entry.getValue(), str);
				str.append("</").append(entry.getKey()).append(">");
			} else if (CollectionUtil.isCollection(entry.getValue())) {
				// 列表节点：List
				for (Object ele : ((Collection<?>) entry.getValue())) {
					str.append("<").append(entry.getKey()).append(">");
					if (MapUtil.isMap(ele)) {
						// Map元素
						toXml((Map<?, ?>) ele, str);
					}else {
						// String元素
						str.append(ele);
					}
					str.append("</").append(entry.getKey()).append(">");
				}
			} else if (ArrayUtil.isArray(entry.getValue())) {
				// 数组节点：Array
				for (Object ele : ((Object[]) entry.getValue())) {
					str.append("<").append(entry.getKey()).append(">");
					if (MapUtil.isMap(ele)) {
						// Map元素
						toXml((Map<?, ?>) ele, str);
					}else {
						// String元素
						str.append(ele);
					}
					str.append("</").append(entry.getKey()).append(">");
				}
			} else {
				str.append("<").append(entry.getKey()).append(">").append(entry.getValue()).append("</").append(entry.getKey()).append(">");
			}
		}
		return str.toString();
	}
	
	/**
	 * 设置默认的类型转换器
	 * 
	 * @param xStream
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年10月11日
	 */
	private static void setConverters(XStream xStream) {
		xStream.registerConverter(XStreamMapSsConverter.INSTANCE);
		xStream.registerConverter(XStreamIntegerConverter.INSTANCE);
		xStream.registerConverter(XStreamIntConverter.INSTANCE);
		xStream.registerConverter(XStreamLongerConverter.INSTANCE);
		xStream.registerConverter(XStreamLongConverter.INSTANCE);
		xStream.registerConverter(XStreamDecimalConverter.INSTANCE);
		xStream.registerConverter(XStreamLocalDateConverter.INSTANCE);
		xStream.registerConverter(XStreamLocalDateTimeConverter.INSTANCE);
	}
	
	/**
	 * 获取XStream
	 * 
	 * @param classAlias		类标签别名：包含主类、集合属性元素类；也可以通过类注解@XStreamAlias指定
	 * @param fieldAlias		字段标签别名：包含主类、属性类、集合属性元素类的字段，别名为null表示排除字段；也可以通过类字段注解@XStreamAlias指定
	 * @param collectionMerges	集合合并处理：将默认的"<集合><元素类>值</集合></元素类>"格式合并为"<集合>值</集合><集合>值</集合>"
	 * 
	 * @return XStream
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年10月10日
	 */
	private static XStream getXStream(List<XStreamClassAliasParam> classAlias, List<XStreamFieldAliasParam> fieldAlias, List<XStreamCollectionParam> collectionMerges) {
		// 创建XStream，解决 _ 转 __ 的问题
		XStream xStream = new XStream(new XppDriver(new XmlFriendlyNameCoder("-_", "_")));
		// 启用注解扫描
		xStream.autodetectAnnotations(true);
		// 去除安全警告
		xStream.allowTypesByRegExp(ALLOW_TYPE);
		// 去除class属性
		xStream.aliasSystemAttribute(null, "class");
		// 类标签别名
		if (CollectionUtil.isNotEmpty(classAlias)) {
			for (XStreamClassAliasParam el : classAlias) {
				// 设置类标签别名：包含主类、集合元素类
				if (el.getClass() != null) {
					xStream.alias(el.getAlias(), el.getClazz());
				}
			}
		}
		// 字段标签别名
		if (CollectionUtil.isNotEmpty(fieldAlias)) {
			// 遍历类：包含主类、集合元素类、属性类
			for (XStreamFieldAliasParam el : fieldAlias) {
				if (el.getClazz() != null && CollectionUtil.isNotEmpty(el.getField())) {
					// 遍历字段
					for (Entry<String, String> entry : el.getField().entrySet()) {
						// 字段名非空
						if (StringUtils.isNotBlank(entry.getKey())) {
							// 别名为空
							if (StringUtils.isBlank(entry.getValue())) {
								// 排除类的字段
								xStream.omitField(el.getClazz(), entry.getKey());
							} else {
								// 设置字段别名
								xStream.aliasField(entry.getValue(), el.getClazz(), entry.getKey());
							}
						}
					}
				}
			}
		}
		// 集合合并处理
		if (CollectionUtil.isNotEmpty(collectionMerges)) {
			for (XStreamCollectionParam el : collectionMerges) {
				if (el.getEleName() == null) {
					el.setEleName(el.getName());
				}
				xStream.addImplicitCollection(el.getEntityClass(),el.getName(),el.getEleName(),el.getEleClass());
			}
		}
		// 转换器
		setConverters(xStream);
		return xStream;
	}
	
}
